## board.py

from typing import List, Tuple
import random
from constants import DIRECTIONS, TILE_SIZE, MARGIN, COLORS


class Board:
    """
    Class to manage the game board for the 2048 game. It handles the initialization
    of the board, moving tiles, adding new tiles, and checking available cells.
    """

    def __init__(self, size: int = 4):
        """
        Initialize the Board class with a default size.

        Args:
        size (int): The size of the board (e.g., 4 for a 4x4 board). Default is 4.
        """
        self._size: int = size
        self._tiles: List[List[int]] = []
        self.init_board()

    def init_board(self) -> None:
        """
        Initialize the board with empty tiles and add two initial tiles with value 2 or 4.
        """
        self._tiles = [[0 for _ in range(self._size)] for _ in range(self._size)]
        self.add_new_tile()
        self.add_new_tile()

    def move_tiles(self, direction: str) -> bool:
        """
        Move the tiles in the specified direction.

        Args:
        direction (str): The direction to move the tiles. Must be one of the values from constants.DIRECTIONS.

        Returns:
        bool: True if the move was successful, False otherwise.
        """
        if direction not in DIRECTIONS.values():
            raise ValueError(f"Invalid direction: {direction}. Allowed directions are: {DIRECTIONS.values()}")

        moved = False
        if direction == "UP":
            moved = self._move_up()
        elif direction == "DOWN":
            moved = self._move_down()
        elif direction == "LEFT":
            moved = self._move_left()
        elif direction == "RIGHT":
            moved = self._move_right()

        return moved

    def add_new_tile(self) -> None:
        """
        Add a new tile (with value 2 or 4) to an available cell on the board.
        """
        available_cells: List[Tuple[int, int]] = self.get_available_cells()
        if available_cells:
            x, y = random.choice(available_cells)
            self._tiles[x][y] = 2 if random.random() < 0.9 else 4

    def get_available_cells(self) -> List[Tuple[int, int]]:
        """
        Get a list of available cells (cells with value 0) on the board.

        Returns:
        List[Tuple[int, int]]: A list of (row, column) tuples representing the available cells.
        """
        available_cells = []
        for i in range(self._size):
            for j in range(self._size):
                if self._tiles[i][j] == 0:
                    available_cells.append((i, j))
        return available_cells

    def get_score(self) -> int:
        """
        Calculate and return the current score. The score is the sum of all tile values.

        Returns:
        int: The current score.
        """
        return sum(sum(row) for row in self._tiles)

    def _move_up(self) -> bool:
        """
        Move all tiles up.

        Returns:
        bool: True if the move was successful, False otherwise.
        """
        moved = False
        for j in range(self._size):
            column = [self._tiles[i][j] for i in range(self._size)]
            merged_column = self._merge_line(column)
            for i in range(self._size):
                if self._tiles[i][j] != merged_column[i]:
                    moved = True
                self._tiles[i][j] = merged_column[i]
        return moved

    def _move_down(self) -> bool:
        """
        Move all tiles down.

        Returns:
        bool: True if the move was successful, False otherwise.
        """
        moved = False
        for j in range(self._size):
            column = [self._tiles[i][j] for i in range(self._size)]
            merged_column = self._merge_line(column[::-1])[::-1]
            for i in range(self._size):
                if self._tiles[i][j] != merged_column[i]:
                    moved = True
                self._tiles[i][j] = merged_column[i]
        return moved

    def _move_left(self) -> bool:
        """
        Move all tiles left.

        Returns:
        bool: True if the move was successful, False otherwise.
        """
        moved = False
        for i in range(self._size):
            row = self._tiles[i]
            merged_row = self._merge_line(row)
            for j in range(self._size):
                if self._tiles[i][j] != merged_row[j]:
                    moved = True
                self._tiles[i][j] = merged_row[j]
        return moved

    def _move_right(self) -> bool:
        """
        Move all tiles right.

        Returns:
        bool: True if the move was successful, False otherwise.
        """
        moved = False
        for i in range(self._size):
            row = self._tiles[i]
            merged_row = self._merge_line(row[::-1])[::-1]
            for j in range(self._size):
                if self._tiles[i][j] != merged_row[j]:
                    moved = True
                self._tiles[i][j] = merged_row[j]
        return moved

    def _merge_line(self, line: List[int]) -> List[int]:
        """
        Merge a single row or column of tiles.

        Args:
        line (List[int]): A list representing a row or column of tiles.

        Returns:
        List[int]: The merged row or column.
        """
        merged_line = [0] * self._size
        i = 0
        j = 0
        while j < self._size:
            if line[j] == 0:
                j += 1
                continue
            elif j < self._size - 1 and line[j] == line[j + 1]:
                merged_line[i] = line[j] * 2
                j += 2
            else:
                merged_line[i] = line[j]
                j += 1
            i += 1
        return merged_line

# Example usage for testing purposes
if __name__ == "__main__":
    board = Board(4)
    print("Initial Board:")
    for row in board._tiles:
        print(row)

    print("\nMoving tiles to the left:")
    board.move_tiles("LEFT")
    for row in board._tiles:
        print(row)

    print("\nMoving tiles to the right:")
    board.move_tiles("RIGHT")
    for row in board._tiles:
        print(row)

    print("\nMoving tiles up:")
    board.move_tiles("UP")
    for row in board._tiles:
        print(row)

    print("\nMoving tiles down:")
    board.move_tiles("DOWN")
    for row in board._tiles:
        print(row)
